跳到主要内容

Detours 管理钩子

Detours 技术简介

Detours 技术利用了修改内存中的程序指令的能力,主要是通过改变函数调用的目标地址,使得函数调用转向用户定义的代码。这种技术可以用来拦截系统调用、API 调用、或者其他函数调用,实现功能如下:

  • 监控和记录 函数调用。
  • 修改 函数参数。
  • 改变执行流程,例如防止特定功能执行。

Detours 技术经常用于拦截和重定向 Windows API 调用。以下是一个使用 Microsoft Detours 库在 C++ 中实现 API 钩子的基本例子。这个例子将展示如何拦截 MessageBox 函数调用,并在实际显示消息框前执行一些自定义代码。

首先,你需要下载并设置 Microsoft Detours 库,这可以从其 GitHub 仓库 中找到。

简单的 MessageBox 拦截例子

步骤 1: 包含必要的头文件

#include <windows.h>
#include <detours.h>
#include <iostream>

步骤 2: 定义原始函数的指针

这将用于保存原始 MessageBoxW 函数的地址,以便在拦截后调用原始函数。

static int (WINAPI *TrueMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;

步骤 3: 实现拦截函数

这个函数将替代原始的 MessageBoxW 函数。

int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) {
MessageBoxW(NULL, L"这是在原始 MessageBox 之前拦截的消息!", L"拦截!", MB_OK);
// 调用原始 MessageBoxW 函数
return TrueMessageBoxW(hWnd, lpText, lpCaption, uType);
}

步骤 4: 在 main 函数中安装和卸载钩子

int main() {
// 挂钩
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueMessageBoxW, DetourMessageBoxW);
DetourTransactionCommit();

// 测试拦截的 MessageBox
MessageBoxW(NULL, L"原始消息框文本", L"测试", MB_OK);

// 卸钩
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueMessageBoxW, DetourMessageBoxW);
DetourTransactionCommit();

return 0;
}

Detours 事务是什么

在 Detours 中,“事务” 是一个重要的概念,它用于确保一系列拦截和恢复操作的原子性。通过使用事务,可以保证多个钩子操作要么全部成功,要么全部失败,不会处于中间状态。这对于确保系统稳定性和避免潜在的错误是非常重要的。

事务的工作机制

事务通过以下几个步骤来确保操作的原子性和一致性:

  1. 开始事务:使用 DetourTransactionBegin 开始一个新的事务。
  2. 更新线程:使用 DetourUpdateThread 告诉 Detours 哪个线程要进行修改。这一步通常在拦截多线程应用程序时是必要的。
  3. 附加或分离钩子:使用 DetourAttach 附加钩子,或使用 DetourDetach 分离钩子。可以在一个事务中进行多个钩子操作。
  4. 提交事务:使用 DetourTransactionCommit 提交事务,应用所有的钩子更改。
  5. 中止事务:如果在提交事务之前发生错误,可以使用 DetourTransactionAbort 中止事务,取消所有未提交的更改。

事务 API 详解

  1. DetourTransactionBegin()

    LONG DetourTransactionBegin(VOID);
    • 开始一个新的 Detours 事务。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。
  2. DetourUpdateThread()

    LONG DetourUpdateThread(HANDLE hThread);
    • 更新指定线程的状态,以准备进行 Detours 操作。
    • 参数:hThread 是一个线程句柄,通常是当前线程 (GetCurrentThread() 返回值)。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。
  3. DetourAttach()

    LONG DetourAttach(PVOID *ppPointer, PVOID pDetour);
    • 附加一个钩子,将原始函数指针替换为钩子函数指针。
    • 参数:
      • ppPointer 是指向原始函数指针的指针。
      • pDetour 是钩子函数的指针。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。
  4. DetourDetach()

    LONG DetourDetach(PVOID *ppPointer, PVOID pDetour);
    • 分离一个钩子,恢复原始函数指针。
    • 参数:
      • ppPointer 是指向原始函数指针的指针。
      • pDetour 是钩子函数的指针。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。
  5. DetourTransactionCommit()

    LONG DetourTransactionCommit(VOID);
    • 提交事务,应用所有的钩子更改。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。
  6. DetourTransactionAbort()

    LONG DetourTransactionAbort(VOID);
    • 中止事务,取消所有未提交的更改。
    • 返回值:如果成功,返回 NO_ERROR;否则,返回错误代码。

示例中的事务操作

在示例代码中,我们看到了如何使用这些 API:

// 开始事务
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

// 附加钩子
DetourAttach(&(PVOID&)RealMessageBox, MyMessageBox);

// 提交事务
DetourTransactionCommit();

这些步骤保证了我们在安装钩子时的操作是原子性的。假设有多个钩子操作需要执行,通过事务机制可以确保它们要么全部成功,要么在遇到错误时全部撤销,确保系统的一致性和稳定性。

// 卸载钩子
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)RealMessageBox, MyMessageBox);
DetourTransactionCommit();

类似地,在卸载钩子时,我们也使用事务来确保操作的原子性和一致性。

".detour" 导出项的作用

在某些案例中,开发者或者逆向工程师可能会在 DLL(动态链接库)中导出特定标记为 ".detour" 的函数或数据,表明这些是用于被外部调用或拦截的钩子。这样的导出项通常用来指示该部分代码或数据是专门用于插入或重定向原有执行流的。

例如,一个软件可能检查其加载的 DLL 是否包含 ".detour" 导出项来确定该 DLL 是否可能包含钩子或拦截代码,这对于安全软件来说是一个重要的安全检查功能。